package cc.avatar.repository;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import cc.avatar.model.Location;
import cc.avatar.model.Product;
import cc.avatar.model.Warehousing;
import cc.avatar.model.WarehousingItem;
import cc.avatar.model.dao.AccountingEntryDao;
import cc.avatar.model.dao.InventoryDao;
import cc.avatar.model.dao.LocationDao;
import cc.avatar.model.dao.PartyDao;
import cc.avatar.model.dao.ProductDao;
import cc.avatar.model.dao.WarehousingDao;
import cc.avatar.model.dto.QueryDto;
import cc.avatar.model.dto.WarehousingItemDto;
import cc.avatar.model.inventory.Inventory;
import cc.avatar.model.inventory.InventoryChange;
import cc.avatar.model.inventory.InventoryChangeEvent;
import cc.avatar.model.inventory.InventoryChangeType;
import cc.avatar.model.inventory.LocatedInventory;
import cc.avatar.model.inventory.StockInLocation;
import cc.avatar.util.DateUtil;
import cc.avatar.util.Export2ExcelUtil;

public class ProductRepository implements ApplicationContextAware {
	private ProductDao productDao;
	private InventoryDao inventoryDao;
	private WarehousingDao warehousingDao;
	private PartyDao partyDao;
	private LocationDao locationDao;
	private TrackingNumberCounter counter;
	private AccountingEntryDao accountingEntryDao;
	private ApplicationContext applicationContext;
	   
    /**
     * @param applicationContext the applicationContext to set
     */
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
       this.applicationContext = applicationContext;
    }
    
	//cached and re-calculated per day, key is product ID
	private Map<Long, Inventory> inventories = new LinkedHashMap<Long, Inventory>();

	// IOC setter
	public void setProductDao(ProductDao productDao) {
		this.productDao = productDao;
	}
	public void setLocationDao(LocationDao locationDao) {
		this.locationDao = locationDao;
	}
	public void setCounter(TrackingNumberCounter counter) {
		this.counter = counter;
	}
	public void setAccountingEntryDao(AccountingEntryDao accountingEntryDao) {
		this.accountingEntryDao = accountingEntryDao;
	}
	public void setWarehousingDao(WarehousingDao warehousingDao) {
		this.warehousingDao = warehousingDao;
	}
	public void setInventoryDao(InventoryDao inventoryDao) {
		this.inventoryDao = inventoryDao;
	}
	public void setPartyDao(PartyDao partyDao) {
		this.partyDao = partyDao;
	}
	// list all product, include current stock
	public List<Product> getAllProduct() {
		return productDao.getAll();
	}

	public Product getProductById(Long id){
		return productDao.getById(id);
	}
	//新建或修改产品信息
	public void saveProduct(Product p) {
		// check the name does not exist if it is 'add' mode.
		productDao.save(p);
		calculateInventories();
	}
	
	public void saveWarehousingBill(List<WarehousingItemDto> items, Long makerId, int usageDelayDays){
		Warehousing wh = new Warehousing();
		wh.setUsageDelayDays(usageDelayDays);
		wh.setMaker(partyDao.getById(makerId));
		for (WarehousingItemDto o : items){
		    WarehousingItem item = new WarehousingItem();
		    item.setQuantityOfBlock(o.getQuantityOfBlock());
		    item.setQuantityOfColumn(o.getQuantityOfColumn());
		    item.setProduct(productDao.getById(o.getProductId()));
		    item.setLocation(locationDao.getById(o.getLocationId()));
		    wh.addItem(item);
		}
		wh.setWhenOccurred(new Date());
		wh.setTrackingNumber(counter.createWarehousingTrackingNumber());
		for (WarehousingItem item : wh.getItems()){
			InventoryChange ic = new InventoryChange();
	        ic.setProduct(item.getProduct());
	        ic.setUsageDelayDays(wh.getUsageDelayDays());
	        ic.setType(InventoryChangeType.produce_in);
	        ic.setCreatedTime(wh.getWhenOccurred());
	        ic.setLocation(item.getLocation());
	        //按块数统计库存
	        ic.setQuantity(item.getQuantityOfBlock());
	        ic.setComments(wh.getMaker().getName() + " " + wh.getTrackingNumber());
			this.inventoryDao.save(ic);
		}	
		
		this.warehousingDao.save(wh);
		//生产成本帐目计算
		accountingEntryDao.save(wh.process());
		//重新统计库存
		applicationContext.publishEvent(new InventoryChangeEvent(wh));
	}
	
	public Warehousing getWarehousingById(Long id) {
		return warehousingDao.getById(id);
	}
	
	@SuppressWarnings("unchecked")
	public List<Warehousing> findWarehousings(QueryDto query){
		StringBuilder hql = new StringBuilder("from Warehousing wh where 1=1");
		List params = new ArrayList();
		if (!StringUtils.isEmpty(query.getTrackingNumber())){
			hql.append(" and wh.trackingNumber like ?");
			params.add("%" + query.getTrackingNumber() + "%");
		}
		if (query.getMaker() != null){
			hql.append( "and wh.maker.id = ? ");
			params.add(query.getMaker());
		}
		if (query.getStartDate() != null){
			DateFormat df = new java.text.SimpleDateFormat("yyyy-MM-dd");
			Date start;
			try {
				start = df.parse(query.getStartDate());
				hql.append(" and wh.whenOccurred between ? and ?");				
				params.add(start);
				//如果没有设置则为取某天数据
				if (StringUtils.isEmpty(query.getEndDate()))				
					params.add(DateUtil.getNextDay(df.parse(query.getStartDate())));
				else
					params.add(df.parse(query.getEndDate()));
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}	
		return warehousingDao.getHibernateTemplate().find(hql.toString(), params.toArray());
	}
	
	public List<Location> getAllLocation(){
		return locationDao.getAll();
	}
	
	public void saveLocation(Long id, String name){
		Location loc;
		if (id == null || id == 0)
		    loc = new Location();
		else
			loc = locationDao.getById(id);
		
		loc.setName(name);
		locationDao.save(loc);	    
	}
	
	public void saveInventoryChange(Long productId, InventoryChangeType type, Long locationId, int amount, String comments){
		if (amount == 0)
			return;
		InventoryChange ic = new InventoryChange();
		ic.setProduct(this.getProductById(productId));
		ic.setType(type);
		ic.setLocation(locationDao.getById(locationId));
		ic.setComments(comments);
		ic.setQuantity(amount);

		ic.setCreatedTime(new Date());
		this.inventoryDao.save(ic);
		applicationContext.publishEvent(new InventoryChangeEvent(ic));
	}
	
	public Collection<Inventory> getInventories(){
		if (this.inventories.isEmpty())
			calculateInventories();
		return this.inventories.values();	
	}
	
	@SuppressWarnings("unchecked")
	public List<InventoryChange> getInventoryHistories(Long productId, int recentDays){
		if (recentDays <= 0){
			String hql = "from InventoryChange ic where ic.product.id = ? order by ic.createdTime desc";			
			return inventoryDao.getHibernateTemplate().find(hql, productId);
		} else {
			String hql = "from InventoryChange ic where ic.product.id = ? and ic.createdTime > ? order by ic.createdTime desc";			
			return inventoryDao.getHibernateTemplate().find(hql, new Object[]{productId, DateUtil.getDateBeforeNow(recentDays)});
		}
	}

	//重新计算库存，这个方法应该在每个涉及库存变动的业务发生后，在零时也应在后台由自动任务执行一次。
	protected void calculateInventories(){
		List<Product> products = this.getAllProduct();
		for (Product p:products){
			Inventory inventory = inventories.get(p.getId());
			if (inventory == null){
				inventory = new Inventory();
			}
			inventory.setProduct(p);
			inventories.put(p.getId(), inventory);
			//-1 means all histories.
			inventory.sum(getInventoryHistories(p.getId(), -1));
		}		
	}
	
	public List<LocatedInventory> getLocatedInventoriesByProductId(Long id){
		if (this.inventories.isEmpty())
			calculateInventories();
		Inventory inv = this.inventories.get(id);
		return inv!=null?inv.getLocatedInventories():new ArrayList<LocatedInventory>();
	}
	
	public List<StockInLocation> getStocksInLocation(Long locationId){
		if (this.inventories.isEmpty())
			calculateInventories();
		Location loc = this.locationDao.getById(locationId);
		List<StockInLocation> results = new ArrayList<StockInLocation>();
		for (Long pid : this.inventories.keySet()){
			Inventory inv = this.inventories.get(pid);
			StockInLocation stock = inv.getStockInLocation(loc);
			if (stock != null)
				results.add(stock);
		}
		return results;
	}
	
	//库存表矩阵
	public void inventories2excel(){
		if (this.inventories.isEmpty())
			calculateInventories();
		//有库存的产品型号数量
		int productInstockCount = this.inventories.keySet().size();
		
		int stockCount = locationDao.getAll().size();

		String[][] stocks = new String[stockCount + 1][productInstockCount + 1];
		
		int rowIndex = 1;
		Map<Long, Integer> locationRowIndex = new HashMap<Long, Integer>();
		Product p;
		Inventory inv;
		int productColIndex = 1;
		
		//循环产品
		for (Long pid: this.inventories.keySet()){
			p = productDao.getById(pid);
			//列头
			stocks[0][productColIndex] = p.getName();
			
			inv = inventories.get(pid);
			for (Location loc : inv.getQuantities().keySet()){
				Integer row = locationRowIndex.get(loc.getId());
				if (row == null){
					//remember row index
					locationRowIndex.put(loc.getId(), rowIndex);					
					row = rowIndex;
					//左侧Location名称
					stocks[row][0] = loc.getName();
					rowIndex++;
				}
				int qty = inv.getQuantities().get(loc);
				int qtySale = inv.getQuantitiesForSale().get(loc);
				
				if (qty == qtySale){
					stocks[row][productColIndex] = String.valueOf(qty);
				} else {
					stocks[row][productColIndex] = String.valueOf(qtySale) + "/" + String.valueOf(qty);
				}
			}
			productColIndex++ ;			
		}
		
        Export2ExcelUtil.export(stocks);
	}

}
